
using Microsoft.Net.Http.Headers;
using LyxWaf.Services.Files;
using NLog.Extensions.Logging;
using System.Text.Unicode;
using System.Text;

namespace LyxWaf.Middleware;

public class FileProviderMiddleware(RequestDelegate next)
{
    private static readonly ILogger _logger = LoggerFactory.Create(builder => builder.AddNLog()).CreateLogger<StatisticLogMiddleware>();
    private readonly RequestDelegate _next = next;

    public async Task InvokeAsync(HttpContext context)
    {
        var fileService = context.RequestServices.GetRequiredService<IFileService>();
        var exists = context.Request.RouteValues.ContainsKey("file-all");
        
        
        if (!exists)
        {
            await _next(context);
            return;
        }
        var host = context.Request.Host.Host;
        var path = context.Request.RouteValues["file-all"]?.ToString() ?? "";
        var prefix = context.Request.Path.ToString();

        if (!prefix.EndsWith('/') && path.Length == 0)
        {
            context.Response.Redirect(prefix + "/");
            return;
        }
        var remove = "/" + path;
        if (prefix.EndsWith(remove))
        {
            prefix = prefix.Remove(prefix.Length - remove.Length);
        }
        var real = await fileService.GetFileRealAsync(host, prefix, path);
        _logger.LogDebug("请求本地的文件服务:'{}':'{}', 实际映射:{}", prefix, path, real);
        if (real == null)
        {
            context.Response.StatusCode = StatusCodes.Status404NotFound;
            await context.Response.WriteAsync("File not found");
            return;
        }

        if (await HandleConditionalRequest(context, prefix, real, fileService))
            return;

        RangeHeaderValue? rangeHeader = null;
        if (context.Request.Headers.TryGetValue("Range", out var value))
        {
            _ = RangeHeaderValue.TryParse(value.ToString(), out rangeHeader);
        }

        var result = await fileService.GetFileAsync(host, prefix, real, rangeHeader);

        SetResponseHeaders(context, result);
        context.Response.StatusCode = result.StatusCode;
        if (result.StatusCode > 300)
        {
            if (result.ErrMsg != null)
            {
                await context.Response.WriteAsync(result.ErrMsg);
            }
            return;
        }

        if (result.Stream != null)
        {
            try
            {
                await using (result.Stream)
                {
                    await result.Stream.CopyToAsync(context.Response.Body);
                }
            }
            catch (Exception ex)
            {
                context.Response.StatusCode = StatusCodes.Status500InternalServerError;
                await context.Response.WriteAsync($"Error reading file: {ex.Message}");
            }
        }
    }

    static async Task<bool> HandleConditionalRequest(HttpContext context, string prefix, string path, IFileService fileService)
    {
        var metadata = await fileService.GetFileMetadataAsync(path);

        if (context.Request.Headers.ContainsKey("If-None-Match"))
        {
            var clientETag = context.Request.Headers.IfNoneMatch.ToString();
            if (clientETag == metadata.ETag)
            {
                context.Response.StatusCode = StatusCodes.Status304NotModified;
                return true;
            }
        }

        if (context.Request.Headers.ContainsKey("If-Modified-Since"))
        {
            if (DateTime.TryParse(context.Request.Headers.IfModifiedSince, out var ifModifiedSince))
            {
                if (metadata.LastModified <= ifModifiedSince.ToUniversalTime())
                {
                    context.Response.StatusCode = StatusCodes.Status304NotModified;
                    return true;
                }
            }
        }
        return false;
    }

    static void SetResponseHeaders(HttpContext context, FileServiceResult result)
    {
        context.Response.Headers.ContentType = result.ContentType;
        context.Response.Headers.ContentLength = result.FileLength;
        context.Response.Headers.LastModified = result.LastModified.ToString("R");
        context.Response.Headers.ETag = $"\"{result.ETag}\"";
        context.Response.Headers.AcceptRanges = "bytes";

        if (result.StatusCode == StatusCodes.Status206PartialContent)
        {
            context.Response.Headers.ContentRange =
                $"bytes {result.RangeStart}-{result.RangeEnd}/{result.TotalLength}";
        }
    }
}